<?php
/**
 * BUtils class.
 * BUtils is the helper static class for business portal.
 * 
 * Yawii Business 版权所有 @ 2012.
 */
class BUtils {
	/**
	 * GKEY is a global encryption key. DON'T CHANGE!!!
	 */
	const GKEY = 'yamii.fi';
	
	/**
	 * Return a shorten url.
	 * @param string $url
	 */
	public static function shortenUrl($url) {
		//自定义生成MD5加密字符串的混合KEY
		$key = '';
		//对传入网址进行MD5加密
		$md5hex = md5($url . $key);
		//要使用生成URL的字符
		$hash = sprintf('%u', crc32($md5hex));
		
		return self::encode($hash);
	}
	
    public static function encode($number, $charsets = 'uppercase') {
		if ($charsets === 'uppercase') $chars = '123456789ABCDEFGHJKLMNPQRSTUXZ';
		else if ($charsets === 'lowercase') $chars = '123456789abcdefghijkmnpqrstuxz';
		else $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
		
		if(!isset($number)){
			return null;
		}

		$base = strlen($chars);
		$out = "";
		for ($t = floor(log($number, $base)); $t >= 0; $t--) {
			$bcp = bcpow($base, $t);
			$a   = floor($number / $bcp) % $base;
			$out = $out . substr($chars, $a, 1);
			$number  = $number - ($a * $bcp);
		}
		$out = strrev($out);
		return $out;
    }

    public static function decode($string, $charsets = 'uppercase'){
		if ($charsets === 'uppercase') $chars = '123456789ABCDEFGHJKLMNPQRSTUXZ';
		else if ($charsets === 'lowercase') $chars = '123456789abcdefghijkmnpqrstuxz';
		else $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
		
		$base = strlen($chars);
		$string  = strrev($string);
		$out = 0;
		$len = strlen($string) - 1;

		for ($t = 0; $t <= $len; $t++){
			$bcpow = bcpow($base, $len - $t);
			$out   = $out + strpos($chars, substr($string, $t, 1)) * $bcpow;
		}

		$out = sprintf('%F', $out);
		$out = substr($out, 0, strpos($out, '.'));
		return $out;
    }
	
	/**
	 * Parse a mcode to a merchant ID.
	 * @param string $url A place url: http://yamii.fi/mqr/100035, http://www.yamii.fi/mqr/100035, yamii.fi/mqr/100035
	 * @return int merchant ID
	 */
	public static function parseMCodeToMId($url)
	{
		$prefix = 'yamii.fi/mqr/';
		$pos = stripos($url, $prefix);
		if (false === $pos) {
			return (int)$url;
		} else {
			$id_merchant = substr($url, $pos + strlen($prefix));
			return (int)$id_merchant;
		}
	}
	
	/**
	 * Create a mcode with the given merchant ID.
	 * @param int $id_merchant
	 * @return string place URL
	 */
	public static function createMCodeByMId($id_merchant) {
		return 'http://yamii.fi/mqr/' . $id_merchant;
	}
	
	/**
	 * get cookie with array
	 * @param string $key 
	 */
	public static function getCookie($key) {
		if (isset(Yii::app()->request->cookies[$key])) {
			$string_value = Yii::app()->request->cookies[$key]->value;
			return json_decode($string_value, true);
		} else {
			return null;
		}
	}
	
	/**
	 * set cookie with array
	 * @param string $key
	 * @param array $value 
	 */
	public static function setCookie($key, $value) {
		$value_json_string = json_encode($value);
		$cookie = new CHttpCookie($key, $value_json_string);
		$cookie->path = "/";
		$cookie->expire = time() + 3600*24*180;  // keep 180 days
		Yii::app()->request->cookies[$key] = $cookie;
	}
	
	/**
	 * clear the cookie.
	 * @param type $key 
	 */
	public static function clearCookie($key) {
		unset(Yii::app()->request->cookies[$key]);
	}
	
	public static function CategoriesNo() {
		return array(
			'all' => 0,
			'arts,culture' => 1,
			'electronics,appliances' => 2,
			'entertainment,recreation' => 3,
			'exercise,sports' => 4,
			'fashion,shopping' => 5,
			'food,drink' => 6,
			'health,beauty' => 7,
			'restaurants' => 8,
			'services' => 9,
			'others' => 10,
		);
	}
	
	/**
	 * Get week day keys.
	 */
	public static function WeekDayKeys($number = 7, $abbr = false) {
		$postfix = $abbr ? '_b' : '';
		
		if ($abbr) {
			return array(
				'1' => $GLOBALS['LNG']['week_1_lbl'],
				'2' => $GLOBALS['LNG']['week_2_lbl'],
				'3' => $GLOBALS['LNG']['week_3_lbl'],
				'4' => $GLOBALS['LNG']['week_4_lbl'],
				'5' => $GLOBALS['LNG']['week_5_lbl'],
				'6' => $GLOBALS['LNG']['week_6_lbl'],
				'7' => $GLOBALS['LNG']['week_7_lbl'],
			);
		}
		
		if ($number == 7) {
			return array(
				'1' => $GLOBALS['LNG']['monday_lbl' . $postfix],
				'2' => $GLOBALS['LNG']['tuesday_lbl' . $postfix],
				'3' => $GLOBALS['LNG']['wednesday_lbl' . $postfix],
				'4' => $GLOBALS['LNG']['thursday_lbl' . $postfix],
				'5' => $GLOBALS['LNG']['friday_lbl' . $postfix],
				'6' => $GLOBALS['LNG']['saturday_lbl' . $postfix],
				'7' => $GLOBALS['LNG']['sunday_lbl' . $postfix],
			);
		} else {
			return array(
				'1' => $GLOBALS['LNG']['monday_lbl' . $postfix],
				'2' => $GLOBALS['LNG']['tuesday_lbl' . $postfix],
				'3' => $GLOBALS['LNG']['wednesday_lbl' . $postfix],
				'4' => $GLOBALS['LNG']['thursday_lbl' . $postfix],
				'5' => $GLOBALS['LNG']['friday_lbl' . $postfix],
			);
		}
	}
	
	/**
	 * Return all 24 hours
	 * @return array
	 */
	public static function AllHour24() {
		return array(
			'0' => '0:00',
			'30' => '0:30',
			'100' => '1:00',
			'130' => '1:30',
			'200' => '2:00',
			'230' => '2:30',
			'300' => '3:00',
			'330' => '3:30',
			'400' => '4:00',
			'430' => '4:30',
			'500' => '5:00',
			'530' => '5:30',
			'600' => '6:00',
			'630' => '6:30',
			'700' => '7:00',
			'730' => '7:30',
			'800' => '8:00',
			'830' => '8:30',
			'900' => '9:00',
			'930' => '9:30',
			'1000' => '10:00',
			'1030' => '10:30',
			'1100' => '11:00',
			'1130' => '11:30',
			'1200' => '12:00',
			'1230' => '12:30',
			'1300' => '13:00',
			'1330' => '13:30',
			'1400' => '14:00',
			'1430' => '14:30',
			'1500' => '15:00',
			'1530' => '15:30',
			'1600' => '16:00',
			'1630' => '16:30',
			'1700' => '17:00',
			'1730' => '17:30',
			'1800' => '18:00',
			'1830' => '18:30',
			'1900' => '19:00',
			'1930' => '19:30',
			'2000' => '20:00',
			'2030' => '20:30',
			'2100' => '21:00',
			'2130' => '21:30',
			'2200' => '22:00',
			'2230' => '22:30',
			'2300' => '23:00',
			'2330' => '23:30',
		);
	}
	
	public static function DetailFeaturesKeys() {
		return array('cuisine_style', 'cuisine_type', 'meal_served', 
			'payment_options', 'service_languages', 'alcohol_license', 
			'dining_ambience', 'car_parking', 'additional_details');
	}
	
	/**
	 * Return html string with weekdays.
	 * @param string $weekdays 1,2,3 | 1,2,3,4,5 | 3,4 | 5
	 */
	public static function HtmlWeekdays($weekdays, $predefined_days) {
		$array = explode(',', $weekdays);
		
		//only one day here
		if (count($array) == 1) {
			if (isset($predefined_days[$array[0]])) {
				return $predefined_days[$array[0]];
			} else {
				return null;
			}
		}
		
		//check if there is no break day
		$isbreak = false;
		$begin = $array[0];
		foreach ($array as $day) {
			if ($begin != $day) {
				$isbreak = true;
				break;
			}
			$begin ++;
		}
		$selected_days = null;
		if ($isbreak) {
			foreach ($array as $day) {
				if (isset($predefined_days[$day])) $selected_days .= "{$predefined_days[$day]} ";
			}
		} else {
			$selected_days = $predefined_days[$array[0]] . ' - ' . $predefined_days[$array[count($array)-1]];
		}
		
		return $selected_days;
	}
	
	/**
	 * Get the recent day name: today, tomorrow, Tus.
	 * @param string $weekdays 1,2,3,4,5,6,7
	 * @return string
	 */
	public static function GetRecentDayName($weekdays, $week_string, $next24, $start_hour) {
		$today_lbl = BUtils::T('today_lbl');
		$tomorrow_lbl = BUtils::T('tomorrow_lbl');
		
		$today_weekno = date('N');
		$tomorrow_weekno = $today_weekno + 1;
		if ($today_weekno == 7) $tomorrow_weekno = 1;
		
		if ($next24) {
			if ($start_hour > date('Hi')) {// the start hour is coming
				$ipos = strpos($weekdays, $today_weekno);
				if ($ipos !== false) {
					return $today_lbl;//find today
				} else {
					$ipos = strpos($weekdays, (string)$tomorrow_weekno);
					if ($ipos !== false) {
						return $tomorrow_lbl;//find tomorrow
					}
				}
			} else {
				$ipos = strpos($weekdays, (string)$tomorrow_weekno);
				if ($ipos !== false) {
					return $tomorrow_lbl;//find tomorrow
				}
			}
		} else {
			$ipos = strpos($weekdays, $today_weekno);
			if ($ipos !== false) {
				return $today_lbl;//find today
			} else {
				$ipos = strpos($weekdays, (string)$tomorrow_weekno);
				if ($ipos !== false) {
					return $tomorrow_lbl;//find tomorrow
				}
			}
		}
		//return week days string
		return $week_string;
	}
	
	/**
	 * Return string with weekdays.
	 * @param string $weekdays 1,2,3 | 1,2,3,4,5 | 3,4 | 5
	 */
	public static function GetWeekdays($weekdays, $week_array) {
		$array = explode(',', $weekdays);
		
		//only one day here
		if (count($array) == 1) {
			if (isset($week_array[$array[0]])) {
				return $week_array[$array[0]];
			} else {
				return null;
			}
		}
		
		//check if there is no break day
		$isbreak = false;
		$begin = $array[0];
		foreach ($array as $day) {
			if ($begin != $day) {
				$isbreak = true;
				break;
			}
			$begin ++;
		}
		$selected_days = null;
		if ($isbreak) {
			foreach ($array as $day) {
				if (isset($week_array[$day])) $selected_days .= "{$week_array[$day]} ";
			}
		} else {
			$selected_days = $week_array[$array[0]] . ' - ' . $week_array[$array[count($array)-1]];
		}
		
		return trim($selected_days);
	}
	
	/**
	 * Get a standard address like "Alex street 18, 26520 Helsinki".
	 */
	public static function GetAddress($street, $zip = null, $city = null) {
		if (!empty($zip)) {
			if (!empty($city)) $street .= '<br />' . $zip . ' ' . $city;
		} else {
			if (!empty($city)) $street .= '<br />' . $city;
		}
		return $street;
	}
	
	/**
	 * Get a standard city like "26520 Helsinki".
	 */
	public static function GetZipCity($zip, $city) {
		if (empty($zip)) {
			return $city;
		} else {
			return $zip . ', ' . $city;
		}
	}
	
	/**
	 * Get the hour range format.
	 * @param string $startTime 1000
	 * @param string $endTime 1530
	 * @param int $showHours 0|1
	 * @return string e.g. 10:00 - 15:30 | Anytime
	 */
	public static function GetHourRangeFormat($startTime, $endTime, $showHours = 1) {
		if ($showHours == 0) {
			return BUtils::T('anytime_lbl');
		}
		
		if (empty($startTime) and empty($endTime)) return null;
		$startTime = sprintf('%04d', $startTime);
		$endTime = sprintf('%04d', $endTime);
		
		if (!empty($startTime)) $startHour = substr($startTime, 0, 2) . ':' . substr($startTime, 2, 2);
		if (!empty($endTime)) $endHour = substr($endTime, 0, 2) . ':' . substr($endTime, 2, 2);
		
		if (empty($startHour)) return $endHour;
		else if (empty($endHour)) return $startTime;
		else return $startHour . ' - ' . $endHour;
	}
	
	/**
	 * Get the end hour format 15:30.
	 * @param string $endTime 1530
	 * @return string e.g. 15:30
	 */
	public static function GetEndHourFormat($endTime) {
		if (empty($endTime) and $endTime!=0) return null;
		$endTime = sprintf('%04d', $endTime);
		return substr($endTime, 0, 2) . ':' . substr($endTime, 2, 2);
	}
	
	/**
	 * Get plain text.
	 */
	public static function GetText($string) {
		return nl2br(CHtml::encode(strip_tags($string)));
	}
	
	/**
	 * Return date range string.
	 * @param int|timestamp $ts_start
	 * @param int|timestamp $ts_end
	 * @param string $format d.m.Y
	 * @return string 
	 */
	public static function GetDateRangeString($ts_start, $ts_end, $format = 'd.m.Y') {
		$returned_string = null;
		$start_date = date($format, $ts_start);
		$end_date = date($format, $ts_end);
		
		if (!empty($ts_start) and !empty($ts_end)) {
			if ($start_date === $end_date) {
				$today = date($format);
				$returned_string = ($today == $start_date) ? BUtils::T('today_lbl') : $start_date;
			} else {
				$returned_string = $start_date . '-' . $end_date;
			}
		} else if ($ts_start > 0) {
			$returned_string = $start_date;
		} else if ($ts_end > 0) {
			$returned_string = $end_date;
		}
		return $returned_string;
	}
	
	/**
	 * Return the until date.
	 * @param int|timestamp $ts_end
	 * @param string $format d.m.Y
	 * @return string 
	 */
	public static function GetUntilDate($ts_end) {
		$format = BUtils::T('php_date_short');
		if (date('Y') != date('Y', $ts_end)) {
			$format = BUtils::T('php_date_long');
		}
		$end_date = date($format, $ts_end);
		return BUtils::T('offer_until_date_lbl', array('{date}'=>$end_date));
	}
	
	/**
	 * Get the standardized category key.
	 * @param string $category_key
	 * @return string
	 */
	public static function GetCategoryKey($category_key) {
		foreach (self::Categories() as $key => $value) {
			if (false !== strpos($key, $category_key)) {
				return $key;
			}
		}
		return 'no_categroy_key';
	}
	
	/**
	 * Get image file extension name.
	 * @param string $type image/gif | image/jpeg | image/png
	 */
	public static function GetImageExtension($type) {
		switch ($type) {
			case 'image/gif':
			case 'gif':
				return 'gif';
			case 'image/jpeg':
			case 'image/jpg':
				return 'jpg';
			case 'image/png':
				return 'png';
			default:
				return '';
		}
	}
	
	/**
	 * Get picture url from business site.
	 * @param string $url_picture
	 */
	public static function GetBusinessPictureUrl($url_picture) {
		if (empty($url_picture)) return Yii::app()->baseUrl . '/img/default_special_thumb.png';
		if (YII_DEBUG) {
			$url_picture = str_replace('yamii.fi/', '../', $url_picture);
			$url_picture = Yii::app()->baseUrl . $url_picture;
			return str_replace('/yamii/', '/business/', $url_picture);
		} else {
			$root = '/business';
			return $root . $url_picture;
		}
	}
	/**
	 * Get picture url from business site.
	 * @param string $url_picture
	 */
	public static function GetOfferPictureUrl($url_picture) {
		if (empty($url_picture)) return Yii::app()->baseUrl . '/img/default_special_picture';
		if (YII_DEBUG) {
			$url_picture = str_replace('yamii.fi/', '../', $url_picture);
			$url_picture = Yii::app()->baseUrl . $url_picture;
			return str_replace('/yamii/', '/business/', $url_picture);
		} else {
			$root = '/business';
			return $root . $url_picture;
		}
	}
	
	/**
	 * Set global language data returned by browser.
	 */
	public static function SetLanguage($lang = null) {
		if (empty($lang)) $lang = Yii::app()->user->getState('lang');	        //get session language
		if (empty($lang)) $lang = BUtils::getCookie('lang');	                //get cookie language
		//if (empty($lang)) $lang = Yii::app()->request->getPreferredLanguage();	//get browser language
		
		$lang = strtolower($lang);
		switch ($lang) {
			case 'fi':
			case 'fi_fi':
				$lang = 'fi_fi';
				$file = 'fi_fi';
				break;
			case 'cn':
			case 'zh':
			case 'zh_cn':
				$lang = 'zh_cn';
				$file = 'zh_cn';
				break;
			default:
				$lang = 'en_gb';
				$file = 'en_gb';
				break;
		}
		if (empty($GLOBALS['LNG']) or $GLOBALS['LNG']['LNG'] != $lang) {
			//load language file
			$path = dirname(__FILE__) . '/../data/lang.'. $file .'.json';
			$path_default = dirname(__FILE__) . '/../data/lang.en_gb.json';
			$GLOBALS['LNG'] = json_decode(file_get_contents($path), true);
			$GLOBALS['LNG_DEFAULT'] = json_decode(file_get_contents($path_default), true);
		}
		//set language
		Yii::app()->setLanguage($lang);
		Yii::app()->user->setState('lang', $lang);
		BUtils::setCookie('lang', $lang);
	}
	
	/**
	 * Get facebook language code.
	 */
	public static function GetFacebookLanguage($lang = null) {
		if (empty($lang)) return 'en_GB';
		switch (strtolower($lang)) {
			case 'fi':
			case 'fi_fi':
				$code = 'fi_FI';
				break;
			case 'chs':
			case 'zh_cn':
				$code = 'zh_CN';
				break;
			case 'zh_tw':
				$code = 'zh_TW';
				break;
			default:
				if (strlen($lang) == 5) {
					$code = substr($lang, 0, 3) . strtoupper(substr($lang, 3, 2));
				} else {
					$code = 'en_GB';
				}
				break;
		}
		return $code;
	}
	
	/**
	 * Created for strip tags (...) with sub string.
	 * @param string $string
	 * @param int $length
	 * @return string 
	 */
	public static function SubString($string, $length = null) {
		if ($length == null) $length = 80;
		$stringDisplay = substr(strip_tags($string), 0, $length);
		if (strlen(strip_tags($string)) > $length) $stringDisplay .= ' ...';
		return $stringDisplay;
	}
	
	/**
	 * Translate languages. Using the default if there is no such key.
	 * @param string $key
	 * @param array(key => value) $params
	 * @return string 
	 */
	public static function T($key, $params = null) {
		if (isset($GLOBALS['LNG'][$key])) {
			$raw = $GLOBALS['LNG'][$key];
		} else {
			$raw = $GLOBALS['LNG_DEFAULT'][$key];
		}
		if (empty($params)) {
			return $raw;
		}
		return str_replace(array_keys($params), array_values($params), $raw);
	}
	
	/**
	 * Get a 32-bit hash value.
	 */
	public static function Hash32($string, $key = self::GKEY) {
		$security = new CSecurityManager();
		return md5($security->hashData($string, $key));
	}
	
	/**
	 * Encrypts a string.
	 * @return string A base64-string.
	 */
	public static function Encrypt($string, $key = self::GKEY) {
		$key = substr(md5($key), 0, 8);
		$iv = '12345678';
		$cipher = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');
		mcrypt_generic_init($cipher, $key, $iv);
		$encrypted = mcrypt_generic($cipher, $string);
		mcrypt_generic_deinit($cipher);
		
		return base64_encode($encrypted);
	}
	
	/**
	 * Decrypts a base64-string.
	 */
	public static function Decrypt($string, $key = self::GKEY) {
		$key = substr(md5($key), 0, 8);
		$string = base64_decode($string);
		$iv = '12345678';
		$cipher = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');
		mcrypt_generic_init($cipher, $key, $iv);
		$decrypted = mdecrypt_generic($cipher, $string);
		mcrypt_generic_deinit($cipher);
		
		return $decrypted;
	}
	
	/**
	 * Send mail to Admin.
	 */
	public static function sendMaiToAdmin($subject, $message, $from) {
		return self::sendMail($subject, $message, $from, 'admin@yamii.fi');
	}
	
	/**
	 * Send mail to Developers.
	 */
	public static function sendMaiToDevelopers($subject, $message, $email_array) {
		if (count($email_array) < 1) {
			return false;
		}
		
		//send email to admin mail
		$mailer = Yii::createComponent('application.extensions.mailer.EMailer');
		$mailer->IsSMTP();
		$mailer->Host = 'mail.yamii.fi';
		$mailer->Port = '26';
		$mailer->Username = 'admin@yamii.fi';
		$mailer->Password = 'yamii_fi';
		$mailer->SMTPAuth = true;
		$mailer->SetFrom('admin@yamii.fi');
		foreach ($email_array as $to) {
			$mailer->AddAddress($to);
		}
		$mailer->Subject = $subject;
		$mailer->Body = $message;
		$mailer->IsHTML(false);
		return $mailer->Send();
	}
	
	/**
	 * Send email to address lists.
	 * 
	 * @param string $subject
	 * @param array $receivers
	 * @param string $htmlText
	 * @param string $plainText
	 * @return boolean
	 */
	public static function SendEmailOut($subject, $receivers, $htmlText, $plainText) {
		if (empty($htmlText)) {
			return true;
		}
		
		//send email from admin mail
		$mailer = Yii::createComponent('application.extensions.mailer.EMailer');
		$mailer->IsSMTP();
		$mailer->Host = 'mail.yamii.fi';
		$mailer->Port = '26';
		$mailer->Username = 'admin@yamii.fi';
		$mailer->Password = 'yamii_fi';
		$mailer->SMTPAuth = true;
		$mailer->SetFrom('admin@yamii.fi', 'Yamii.fi');
		$mailer->AddReplyTo('admin@yamii.fi', 'Yamii.fi');
		if (is_array($receivers)) {
			foreach ($receivers as $email) {
				$mailer->AddAddress($email);
			}
		}
		$mailer->CharSet = 'utf-8';
		$mailer->Subject = $subject;
		$mailer->MsgHTML($htmlText);
		$mailer->AltBody = $plainText;
		return $mailer->Send();
	}
	
	/**
	 * Send mail.
	 */
	private static function sendMail($subject, $message, $from, $to) {
		//send email to admin mail
		$mailer = Yii::createComponent('application.extensions.mailer.EMailer');
		$mailer->IsSMTP();
		$mailer->Host = 'mail.yamii.fi';
		$mailer->Port = '26';
		$mailer->Username = 'admin@yamii.fi';
		$mailer->Password = 'yamii_fi';
		$mailer->SMTPAuth = true;
		$mailer->SetFrom($from);
		$mailer->AddAddress($to);
		$mailer->Subject = $subject;
		$mailer->MsgHTML($message);
		return $mailer->Send();
	}
	
	/**
	 * Async-calling a url with post data
	 * @param string $url
	 * @param array $params
	 */
	public static function curl_post_async($url, $params) {
		foreach ($params as $key => &$val) {
			if (is_array($val)) $val = implode(',', $val);
			$post_params[] = $key.'='.urlencode($val);
		}
		$post_string = implode('&', $post_params);

		$parts = parse_url($url);
		$fp = fsockopen($parts['host'], isset($parts['port'])?$parts['port']:80, $errno, $errstr, 30);

		$out = "POST ".$parts['path']." HTTP/1.1\r\n";
		$out.= "Host: ".$parts['host']."\r\n";
		$out.= "Content-Type: application/x-www-form-urlencoded\r\n";
		$out.= "Content-Length: ".strlen($post_string)."\r\n";
		$out.= "Connection: Close\r\n\r\n";
		if (isset($post_string)) $out.= $post_string;

		fwrite($fp, $out);
		fclose($fp);
	}

	/**
	 * Calculate the distance between two coordinates (latitude, longitude)
	 * @param float $lat1
	 * @param float $lng1
	 * @param float $lat2
	 * @param float $lng2
	 * @param bool $miles
	 * @return float
	 */
	public static function get_distance($lat1, $lng1, $lat2, $lng2, $miles = false) {
		$pi80 = M_PI / 180;
		$lat1 *= $pi80;
		$lng1 *= $pi80;
		$lat2 *= $pi80;
		$lng2 *= $pi80;

		$r = 6372.797; // mean radius of Earth in km
		$dlat = $lat2 - $lat1;
		$dlng = $lng2 - $lng1;
		$a = sin($dlat / 2) * sin($dlat / 2) + cos($lat1) * cos($lat2) * sin($dlng / 2) * sin($dlng / 2);
		$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
		$km = $r * $c;
		return ($miles ? ($km * 0.621371192) : $km);
	}
	
	/** 
	* Return URL-Friendly string slug
	* @param string $string 
	* @return string 
	*/
	public static function seoUrl($string) {
		$bad_words = array('a','and','the','an','it','is','with','can','of','why','not');
		return self::_generate_seo_link($string, '-', true, $bad_words);
	}

	/* takes the input, scrubs bad characters */
	private static function _generate_seo_link($input, $replace = '-', $remove_words = true, $words_array = array()) {
		//replace öäå to oaa
		$input = str_replace(array('ä','å','ö','Ä','Å','Ö','é'), array('a','a','o','a','a','o','e'), $input);

		//Unwanted:  {UPPERCASE} ; / ? : @ & = + $ , . ! ~ * ' ( )
		$return = strtolower($input);
		//Strip any unwanted characters
		$return = preg_replace('/[^a-z0-9_\s-]/', '', $return);
		//Clean multiple dashes or whitespaces
		$return = preg_replace('/[\s-]+/', ' ', $return);
		//Convert whitespaces and underscore to dash
		$return = trim(preg_replace('/[\s_]/', '-', $return));

		//remove words, if not helpful to seo
		//i like my defaults list in remove_words(), so I wont pass that array
		if($remove_words) { $return = self::_remove_words($return, $replace, $words_array); }

		//convert the spaces to whatever the user wants
		//usually a dash or underscore..
		//...then return the value.
		return str_replace(' ', $replace, $return);
	}

	/* takes an input, scrubs unnecessary words */
	private static function _remove_words($input,$replace,$words_array = array(),$unique_words = true)
	{
		//separate all words based on spaces
		$input_array = explode(' ',$input);

		//create the return array
		$return = array();

		//loops through words, remove bad words, keep good ones
		foreach($input_array as $word)
		{
			//if it's a word we should add...
			if(!in_array($word,$words_array) && ($unique_words ? !in_array($word,$return) : true))
			{
				$return[] = $word;
			}
		}

		//return good words separated by dashes
		return implode($replace,$return);
	}

	/**
	 * Filter array with a set of value
	 * 
	 * e.g.
	 * array_filter_values(array('a', 'b', 'c'), array('b', 'c')) => array('a')
	 */
	public static function array_filter_values($main_array, $sub_array) {
		foreach ($sub_array as $hit_value) {
			$main_array = array_filter($main_array, function ($main_item) use ($hit_value) {
				return ($main_item !== $hit_value);
			});
		}
		return $main_array;
	}
	
	/**
	 * for case insensitive array_search.
	 */
	public static function array_search_i($str, $array) {
		if (empty($array)) return false;
		foreach($array as $key => $value) {
			if (!empty($value)) {
				if (stristr($str, $value)) return $key;
			}
		}
		return false;
	}
	
	/**
	 * Search for containing a given array.
	 * @param string $needle
	 * @param array $array
	 * @param bool $sensitive=false if the search is case sensitive.
	 * @return mixed the key of array, or false if needle is not found.
	 */
	public static function array_contain($needle, $array, $sensitive = false) {
		if (empty($array)) return false;
		if ($sensitive) {
			foreach ($array as $key => $value) {
				if (!empty($value)) {
					if (false !== stristr($value, $needle)) {
						return $key;
					}
				}
			}
		} else {
			foreach ($array as $key => $value) {
				if (!empty($value)) {
					if (false !== strstr($value, $needle)) {
						return $key;
					}
				}
			}
		}
		
		return false;
	}
	
	/**
	 * Append an array to array.
	 * @param array $array (ref)
	 * @param array $appending (ref)
	 */
	public static function array_append(&$array, &$appending) {
		foreach ($appending as $item) {
			array_push($array, $item);
		}
	}
	
	/**
	 * Gets discount number
	 * @param float $ori_price 26.8
	 * @param float $new_price 18.8
	 * @param string $type savings | discounts
	 * @return string 89% | -39%
	 */
	public static function GetDiscount($ori_price, $new_price, $type = 'savings') {
		if ($ori_price>0 and $new_price>0) {
			$float = round(($new_price / $ori_price) * 100);
			if ($type == 'savings') {
				return 100 - $float;
			} else {
				return $float - 100;
			}
		} else {
			return null;
		}
	}
	
	/**
	 * get current microtime.
	 * @return float
	 */
	public static function takeMicrotime() {
		list($usec, $sec) = explode(" ", microtime());
		return ((float)$usec + (float)$sec);
	}
	
	/**
	 * Logging the values. /logs/push_201112.log
	 * @param string $key
	 * @param array $values
	 */
	public static function Logging($key, $values, $prefix = '') {
		$log = new BLogger(Yii::app()->basePath . "/runtime/logs/push_" . date('Ym') . "{$prefix}.log", BLogger::INFO);
		$log->LogInfo("{{$key}} -> " . json_encode($values));
	}
	
	/**
	 * Logging the values. /../APILogs/api_none_201112.log
	 * @param string $key
	 * @param array $values
	 */
	public static function APILogging($key, $values, $prefix = 'none') {
		$prefix = str_replace('/', '_', $prefix);	//base64 chars fix for the invalid file name.
		$log = new BLogger(Yii::app()->basePath . "/../APILogs/api_{$prefix}_" . date('Ym') . ".txt", BLogger::INFO);
		$log->LogInfo("{{$key}} -> " . json_encode($values));
	}
	
	public static function LoggingOffers($rows, $controller, $device_type, $user_id) {
		// record to DB
		$sql_insert_log = 'INSERT INTO tbl_merchant_special_listed_log (device_type, id_user, id_special, ip, agent, access_path) 
			VALUES (:device_type, :id_user, :id_special, :ip, :agent, :access_path)';
		$params = array(
			':device_type' => $device_type,
			':id_user' => $user_id,
			':ip' => Yii::app()->request->getUserHostAddress(),
			':agent' => Yii::app()->request->getUserAgent(),
			':access_path' => $controller->module->id . '/' . $controller->id . '/' . $controller->action->id,
		);
		$connection = Yii::app()->db;
		$command = $connection->createCommand($sql_insert_log);
		foreach ($rows as $r) {
			$params[':id_special'] = $r['id'];
			$command->execute($params);
		}
		$connection->active = false;
	}

	/**
	 * Send response with json.
	 * @param type $status
	 * @param type $message
	 */
	public static function sendJSON($status, $message) {
		$json_string = array(
			'code' => $status,
			'message' => $message,
		);
		self::_sendResponse(200, CJSON::encode($json_string), 'application/json');
	}
	protected static function _sendResponse($status = 200, $body = '', $content_type = 'application/json')
	{
		$status_header = 'HTTP/1.1 ' . $status . ' ' . self::_getStatusCodeMessage($status);
		header($status_header);
		header('Content-type: ' . $content_type);
		if ($body != '') {
			echo $body;
		}
		else
		{
			$message = '';
			switch($status)
			{
				case 401:
					$message = 'You must be authorized to view this page.';
					break;
				case 404:
					$message = 'The requested URL ' . $_SERVER['REQUEST_URI'] . ' was not found.';
					break;
				case 500:
					$message = 'The server encountered an error processing your request.';
					break;
				case 501:
					$message = 'The requested method is not implemented.';
					break;
			}
			$signature = ($_SERVER['SERVER_SIGNATURE'] == '') ? $_SERVER['SERVER_SOFTWARE'] . ' Server at ' . $_SERVER['SERVER_NAME'] . ' Port ' . $_SERVER['SERVER_PORT'] : $_SERVER['SERVER_SIGNATURE'];
			$body = array(
				'code' => $status,
				'status' => self::_getStatusCodeMessage($status),
				'message' => $message,
				'signature' => $signature,
			);
			echo CJSON::encode($body);
		}
		Yii::app()->end();
	}
	protected static function _getStatusCodeMessage($status) {
		$codes = Array(
			100 => 'Continue',
			101 => 'Switching Protocols',
			200 => 'OK',
			201 => 'Created',
			202 => 'Accepted',
			203 => 'Non-Authoritative Information',
			204 => 'No Content',
			205 => 'Reset Content',
			206 => 'Partial Content',
			300 => 'Multiple Choices',
			301 => 'Moved Permanently',
			302 => 'Found',
			303 => 'See Other',
			304 => 'Not Modified',
			305 => 'Use Proxy',
			306 => '(Unused)',
			307 => 'Temporary Redirect',
			400 => 'Bad Request',
			401 => 'Unauthorized',
			402 => 'Payment Required',
			403 => 'Forbidden',
			404 => 'Not Found',
			405 => 'Method Not Allowed',
			406 => 'Not Acceptable',
			407 => 'Proxy Authentication Required',
			408 => 'Request Timeout',
			409 => 'Conflict',
			410 => 'Gone',
			411 => 'Length Required',
			412 => 'Precondition Failed',
			413 => 'Request Entity Too Large',
			414 => 'Request-URI Too Long',
			415 => 'Unsupported Media Type',
			416 => 'Requested Range Not Satisfiable',
			417 => 'Expectation Failed',
			500 => 'Internal Server Error',
			501 => 'Not Implemented',
			502 => 'Bad Gateway',
			503 => 'Service Unavailable',
			504 => 'Gateway Timeout',
			505 => 'HTTP Version Not Supported',
		);
		return (isset($codes[$status])) ? $codes[$status] : '';
	}
	
	
	/**
	 * Push Notification to one device iOS/Android(GCM)
	 * 
	 * @param string $push_token - device_notification_token
	 * @param string $device_type - ios/android/wp
	 * @param array $messageData - array('message'=>$message, 'offer_id'=>$id_special);
	 * @param bool $isDevp
	 * @return int 1 - success, 0 - failed
	 */
	public static function PushNotificationToDevice($push_token, $device_type, $messageData, $isDevp = false) {
		$count = 0;
		
		//start to push messages to devices
		if ($device_type == 'ios') {
			self::_pushNotificationIOS($push_token, $messageData, $isDevp);
			$count = 1;
		} elseif ($device_type == 'android') {
			$response = self::_pushNotificationAndroidGCM(array($push_token), $messageData);
			$json_array = json_decode($response, true);
			if ($json_array['success'] == 1) {
				$count = 1;
			} else {
				self::APILogging('notification_failed', $response);
			}
		}
		
		return $count;
	}
	
	/**
	 * IOS push notification.
	 * @param string $push_token - device_notification_token
	 * @param string $messageData['message', 'offer_id']
	 * @throws Exception
	 */
	protected static function _pushNotificationIOS($push_token, $messageData, $isDevp) {
		// Put your private key's passphrase here:
		$passphrase = '1234qwer';//why it's that? bocheng, 2012.12.05
		$pem_file = Yii::app()->basePath . '/data/yamiic_pro.pem';
		if ($isDevp) {
			$pem_file = Yii::app()->basePath . '/data/yamiic_dev.pem';
		}
		
		// Set a context
		$ctx = stream_context_create();
		stream_context_set_option($ctx, 'ssl', 'local_cert', $pem_file);
		stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
		
		// Open a connection to the APNS server
		$fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $err, $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
		if (!$fp) {
			throw new Exception("Failed to connect APNS: {$err} {$errstr}");
		}
		
		// Create the payload body
		$body = array(
			'aps' => array(
				'alert' => $messageData['message'],
				'sound' => 'default',
				'badge' => 1,
			),
			'offer_id' => (string)$messageData['offer_id'],
		);
		
		// Encode the payload as JSON
		$payload = json_encode($body);
		
		// Build the binary notification
		$notification = chr(0) . pack('n', 32) . pack('H*', $push_token) . pack('n', strlen($payload)) . $payload;
		
		// Send it to the server
		$result = fwrite($fp, $notification, strlen($notification));
		if (!$result) {
			throw new Exception("Message not delivered");
		}

		// Close the connection to the server
		fclose($fp);
	}
	
	/**
	 * The following function will send a GCM notification using curl.
	 * 
	 * @param array $registrationIdsArray An array of registration ids to send this notification to
	 * @param array $messageData An named array of data to send as the notification payload
	 * @return type
	 */
	protected static function _pushNotificationAndroidGCM($registrationIdsArray, $messageData) {
		$apiKey = 'AIzaSyDmMjTK6U3waj2uV9uohsMTWfCC1XjTS5Y';
		$headers = array("Content-Type:" . "application/json", "Authorization:" . "key=" . $apiKey);
		$data = array(
			'data' => $messageData,
			'registration_ids' => $registrationIdsArray
		);

		$ch = curl_init();
		curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
		curl_setopt( $ch, CURLOPT_URL, "https://android.googleapis.com/gcm/send" );
		curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0 );
		curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 );
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
		curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode($data) );

		$response = curl_exec($ch);
		curl_close($ch);

		return $response;
	}
}